//  Copyright (c) Microsoft Corporation.  All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Security.Principal;

using System.IdentityModel.Claims;
using System.IdentityModel.Selectors;
using System.IdentityModel.Policy;

namespace EAI.SAML
{
    public class EAISamlAttributeStatement : EAISamlStatement
    {
        readonly ImmutableCollection<EAISamlAttribute> attributes = new ImmutableCollection<EAISamlAttribute>();
        readonly ImmutableCollection<EAISamlEncryptedAttribute> encryptedAttributes = new ImmutableCollection<EAISamlEncryptedAttribute>();

        bool isReadOnly = false;

        public EAISamlAttributeStatement()
        {
        }

        public EAISamlAttributeStatement(XmlReader reader, EAISamlSerializer serializer, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            this.ReadXml(reader, serializer, keyInfoSerializer, outOfBandTokenResolver);
        }

        public override void MakeReadOnly()
        {
            if (!this.isReadOnly)
            {
                this.attributes.MakeReadOnly();
                this.isReadOnly = true;
            }
        }

        public IList<EAISamlAttribute> Attributes
        {
            get
            {
                return this.attributes;
            }
        }

        public IList<EAISamlEncryptedAttribute> EncryptedAttributes
        {
            get
            {
                return this.encryptedAttributes;
            }
        }

        public override IAuthorizationPolicy CreatePolicy(ClaimSet issuer, EAISamlAssertion assertion, EAISamlSecurityTokenAuthenticator tokenAuthenticator)
        {
            List<ClaimSet> claimSets = new List<ClaimSet>();

            if (assertion.Subject != null)
            {
                List<ClaimSet> subjectKeyClaimSet = assertion.Subject.ExtractSubjectKeyClaimSet(assertion, tokenAuthenticator);
                claimSets.AddRange(subjectKeyClaimSet);
            }

            List<Claim> attributeClaims = new List<Claim>();
            foreach (EAISamlAttribute attr in this.attributes)
                attr.AddClaimsToList(attributeClaims);

            foreach (EAISamlEncryptedAttribute encAttr in this.encryptedAttributes)
                encAttr.Attribute.AddClaimsToList(attributeClaims);

            ClaimSet attributeClaimList = new DefaultClaimSet(attributeClaims);

            IIdentity primaryIdentity = (assertion.Subject != null) ? assertion.Subject.PrimaryIdentity : null;

            return new UnconditionalPolicy(primaryIdentity, issuer, claimSets);
        }

        void CheckObjectValidity()
        {
            if (this.attributes.Count == 0)
                throw new InvalidOperationException("saml:AttributeStatement should contain at least one attribute.");
        }

        public override void ReadXml(XmlReader reader, EAISamlSerializer serializer, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            if (serializer == null)
                throw new ArgumentNullException("serializer");

            reader.Read();
            while (reader.IsStartElement())
            {
                if (reader.IsStartElement(EAISamlConstants.Attribute, EAISamlConstants.Namespace))
                {
                    if (this.encryptedAttributes.Count > 0)
                        throw new SecurityTokenException("<saml:AttributeStatement> has more than one child element type specified.");

                    this.attributes.Add(serializer.LoadAttribute(reader, keyInfoSerializer, outOfBandTokenResolver));
                }
                else if (reader.IsStartElement(EAISamlConstants.EncryptedAttribute, EAISamlConstants.Namespace))
                {
                    if (this.attributes.Count > 0)
                        throw new SecurityTokenException("<saml:AttributeStatement> has more than one child element type specified.");

                    this.encryptedAttributes.Add(serializer.LoadEncryptedAttribute(reader, keyInfoSerializer, outOfBandTokenResolver));
                }
            }

            if ((this.attributes.Count == 0) && (this.encryptedAttributes.Count == 0))
                throw new SecurityTokenException("<saml:AttributeStatement> should have atleast one <saml:Attribute> or <saml:EncryptedAttribte> child element.");

            reader.ReadEndElement();
        }

        public override void WriteXml(XmlWriter writer, SecurityTokenSerializer tokenSerializer)
        {
            CheckObjectValidity();

            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement(EAISamlConstants.AttributeStatement, EAISamlConstants.Namespace);
            if (this.attributes.Count > 0)
            {
                for (int i = 0; i < this.attributes.Count; ++i)
                {
                    this.attributes[i].WriteXml(writer, tokenSerializer);
                }
            }
            else if (this.encryptedAttributes.Count > 0)
            {
                for (int i = 0; i < this.encryptedAttributes.Count; ++i)
                {
                    this.encryptedAttributes[i].WriteXml(writer, tokenSerializer);
                }
            }

            writer.WriteEndElement();
        }
    }
}
